/*
 * @Author: 王汝鹏
 * @Date: 2020-11-25 15:41:51
 * @LastEditTime: 2022-03-07 16:30:08
 * @LastEditors: Please set LastEditors
 * @Description: 数据管理对象
 * @FilePath: \x600\data_manager.js
 */
const path = require("path");
const { fork } = require("child_process");

//  ===程序运行配置===
//  子进程任务文件路径
const CHILD_TASK_FILE = path.join(process.cwd(), "/class/child_task.js");
//  数据管理对象缓存的数据长度
const CACHE_DATA_LENGTH = 5;


//  ===程序运行变量===
//  子程序状态字典表
const CHILD_STATUS_MAP = {
  "UNSTART": 0,  //未启动
  "READY"  : 1,  //已就绪
  "EXITED" : 2,  //已退出
}
const { UNSTART, READY, EXITED } = CHILD_STATUS_MAP;
//  父进程结束事件, 结束前杀死子进程, 防止产生孤儿进程
process.on("beforeExit", (code) => {
  if (DataManager.child_handle) {
    console.log("父进程结束, 杀死子进程");
    DataManager.child_handle.kill(9);
    DataManager.child_status = EXITED;
    DataManager.child_handle = null;
  }
})


/**
 * @class DataManager
 * @description 数据管理对象
 * @function [static][async]open  打开数据管理对象
 * @function [static][async]close 关闭数据管理对象
 * @function [static][async]read  读取一个数据,读取之前需保证已打开成功
 */
class DataManager {
  //  缓存的数据
  static Cache_Datas  = [];
  //  子进程对象
  static child_handle = null;
  //  子进程状态
  static child_status = UNSTART;

  /**
   * 启动数据管理对象子进程
   * @param {Number} timeout 启动超时时间,默认(10s)
   * @return {Boolean} 成功:true, 失败:false
   */
  static async open(timeout=10000) {
    //  超时时长(ns)
    let timeout_ns = BigInt(timeout) * BigInt(1000000);

    //  启动开始时间记录(用于判断是否启动超时)
    let start_time = process.hrtime.bigint();
    //  超时时间时刻(ns)
    let timeout_time = start_time + timeout_ns;

    //  子进程为"未启动"状态
    if (DataManager.child_status != READY) {
      //  创建子进程, 执行指定文件的任务
      DataManager.child_handle = fork(CHILD_TASK_FILE);
      console.log(`[主进程号]: ${process.pid}`);
      console.log(`[子进程已创建, 进程号]: ${DataManager.child_handle.pid}`);
      //  监听子进程发送回的消息
      DataManager.child_handle.on("message", DataManager.msg_handle);
      //  子进程结束事件
      DataManager.child_handle.on("exit", DataManager.exit_handle);
    }

    //  等待程序启动(参数1表达式成立), 直到程序启动或时间超时
    //  参数1: 表达式, ()=>DataManager.child_status==READY
    //  参数2: 等待超时时长(ns), timeout_time-process.hrtime.bigint()
    let result = await DataManager.waiting_advices(()=>DataManager.child_status==READY, timeout_time-process.hrtime.bigint());
    if (result == false) return false;

    //  数据已读， 发送命令读取新数据
    if (DataManager.Cache_Datas.length < CACHE_DATA_LENGTH) {
      DataManager.child_handle.send({ type:"read", value:DataManager.Cache_Datas.length });
    }

    //  读取中的话循环等待读取
    //  参数1: 表达式, ()=>DataManager.Cache_Datas.length>=CACHE_DATA_LENGTH
    //  参数2: 等待超时时长(ns), timeout_time-process.hrtime.bigint()
    result = await DataManager.waiting_advices(()=>DataManager.Cache_Datas.length>=CACHE_DATA_LENGTH, timeout_time-process.hrtime.bigint());
    if (result == false) return false;

    return true;
  }

  /**
   * 结束子进程
   * @return {Boolean} 操作结果, 成功:true, 失败:false
   */
  static async close() {
    //  子进程已结束
    if (DataManager.child_status != READY) 
      return true;

    //  杀死子进程
    DataManager.child_handle.kill(9);
    DataManager.child_status = EXITED;
    DataManager.child_status = null;
    return true;
  }

  /**
   * 读取一个文件的数据
   * @param {Number} timeout 超时时间,默认(5s)  
   * @return {Object} 成功:1项数据, 失败:null
   */
  static async read(timeout=5000) {
    //  超时时长(ns)
    let timeout_ns = BigInt(timeout) * BigInt(1000000);

    //  启动开始时间记录(用于判断是否启动超时)
    let start_time = process.hrtime.bigint();
    //  超时时间时刻(ns)
    let timeout_time = start_time + timeout_ns;
    
    //  子程序运行状态检查
    if (DataManager.child_status != READY) {
      console.log("子程序未启动, 请先启动(DataManager.open())后再读数据");
      return null;
    }

    //  检查是否需要向子进程发送补数据通知(缓存数量不足,就会发送)
    if (DataManager.Cache_Datas.length < CACHE_DATA_LENGTH) {
      DataManager.child_handle.send({ type:"read", value:DataManager.Cache_Datas.length });
    }

    //  缓存数据不足,等待子进程发来数据,如果超时返回null
    //  参数1: 表达式, ()=>DataManager.Cache_Datas.length>0
    //  参数2: 等待超时时长(ns), timeout_time-process.hrtime.bigint()
    let result = await DataManager.waiting_advices(()=>DataManager.Cache_Datas.length>0, timeout_time-process.hrtime.bigint());
    if (result == false) return null;
    
    //  数据可读
    if (DataManager.Cache_Datas.length > 0) {
      return DataManager.Cache_Datas.shift();
    }
  }

  /**
   * 子进程回传的消息处理函数
   * @param {Any} msg 消息内容
   */
  static msg_handle(msg) {
    if (msg instanceof Object) {
      //  type:消息类型， value:消息值
      let { type, value } = msg;
  
      //  【信号】子程序已准备就绪
      if (type=="ready") {
        DataManager.child_status = READY;
        console.log(`子程序已就绪`);
      }
  
      //  【信号】接收到了子进程处理后的数据
      if (type == "data") {
        // console.log("value: ", value);
        DataManager.Cache_Datas.push(value);
        // console.log(`已接收到子进程发来的数据`);
      }
    }
  }

  /**
   * 子进程退出事件处理函数
   * @param {Number} code 退出码
   */
  static exit_handle(code) {
    if (DataManager.child_handle) {
      console.log(`[子进程退出]: pid=${DataManager.child_handle.pid}, code=${code}`);
      DataManager.child_status = EXITED;
      DataManager.child_handle = null;
    }
  }

  /**
   * 等待条件成立
   * @param {Function} expr 条件表达式
   * @param {Number} timeout 超时时间(ns)
   * @return {Boolean} true:条件成立, false:超时
   */
  static async waiting_advices(expr, timeout) {
    let time1 = process.hrtime.bigint();

    while(expr() == false) {
      //  等待超时
      if (process.hrtime.bigint()-time1 > timeout) 
        return false;
      
      //  延时等待
      await new Promise((res, rej) => setImmediate(res));
    }
    
    //  条件成立
    return true;
  }
}


module.exports = {
  DataManager
}
